package com.cloud.easy.im.container;

import com.cloud.easy.im.event.ServerEventListener;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;
import io.netty.channel.Channel;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

/**
 * 基于caffeine-cache所实现的高性能本地队列
 * <p>
 * Caffeine提供了三种定时驱逐策略：
 * <p>
 * expireAfterAccess(long, TimeUnit):在最后一次访问或者写入后开始计时，在指定的时间后过期。假如一直有请求访问该key，那么这个缓存将一直不会过期。
 * expireAfterWrite(long, TimeUnit): 在最后一次写入缓存后开始计时，在指定的时间后过期。
 * expireAfter(Expiry): 自定义策略，过期时间由Expiry实现独自计算。
 * 缓存的删除策略使用的是惰性删除和定时删除。这两个删除策略的时间复杂度都是O(1)。
 *
 * @author wangjian
 * @version 0.1.0
 * @create 2020年09月18日9:38 上午
 */
public class CaffeineCache implements OnlineMap {

    private static final Logger logger = LoggerFactory.getLogger(CaffeineCache.class);

    /**
     * 在线队列存储容器
     */
    //private final Cache<K, V> onlineMap;

    private final ConcurrentHashMap<Integer, Cache<String, Channel>> onlineMaps;

    private ServerEventListener serverEventListener;

    public CaffeineCache() {
        this.onlineMaps = new ConcurrentHashMap<>();
        //this.onlineMap = getCache();
        // 基于固定的到期策略进行退出
    }

    public Cache<String, Channel> getCache() {
        return Caffeine.newBuilder()
                .expireAfterAccess(35, TimeUnit.SECONDS)
                //设置内存淘汰监听器
                .removalListener(new RemovalListener() {
                    @Override
                    public void onRemoval(@Nullable Object key, @Nullable Object value, @NonNull RemovalCause cause) {
                        //淘汰过期session,回调通知
                        serverEventListener.onUserLogoutAction_CallBack((String) key, null, (Channel) value);
                        logger.info("[CaffeineCache] key过期淘汰。 key: " + key + "value" + value);
                    }
                })
                .build();
    }

    @Override
    public ConcurrentMap<String, Channel> get(Integer productCode) {
        Cache<String, Channel> cache = onlineMaps.get(productCode);
        if (!Objects.isNull(cache)) {
            return cache.asMap();
        }
        return null;
    }

    @Override
    public Channel get(String userId, Integer productCode) {
        if(Objects.isNull(productCode)){
            logger.error("产品Code为null | userId:{},productCode:{}",userId,productCode);
            return null;
        }
        ConcurrentMap<String, Channel> Cache = get(productCode);
        if (Objects.isNull(Cache)) {
            return null;
        }
        return Cache.get(userId);
    }

    @Override
    public void put(Integer productCode,String userId, Channel value) {
        // 从map获取对应产品容器
        Cache<String, Channel> cache = onlineMaps.get(productCode);
        if (Objects.isNull(cache)) {
            synchronized (this) {
                if (Objects.isNull(onlineMaps.get(productCode))) {
                    // 初始化一个新的容器
                    cache = getCache();
                } else {
                    cache = onlineMaps.get(productCode);
                }
                cache.put(userId, value);
                onlineMaps.put(productCode, cache);
            }
        }else{
            cache.put(userId, value);
            onlineMaps.put(productCode, cache);
        }

    }
    public void check () {
        //for (Cache<String, Channel> value : onlineMaps.values()) {
        //    for (Map.Entry<String, Channel> stringChannelEntry : value.asMap().entrySet()) {
        //        if(stringChannelEntry.getValue() == null){
        //            logger.error("Key = "+stringChannelEntry.getKey()+"为Null!!!!!!!!!!!!!!");
        //        }
        //    }
        //}
        //System.out.println(size());
        //System.out.println(onlineMaps.size());
    }

    @Override
    public void put(String userId, Channel value) {
    }

    @Override
    public boolean containsKey(String userId, Integer productCode) {
        return get(userId, productCode) != null;
    }

    @Override
    public long size() {
        long sum = 0;
        for (Cache<String, Channel> value : onlineMaps.values()) {
            long size = value.estimatedSize();
            sum += size;
        }
        return sum;
    }

    @Override
    public void remove(String userId, Integer productCode) {
        Cache<String, Channel> cache = onlineMaps.get(productCode);
        if (Objects.isNull(cache)) {
            return;
        }
        cache.invalidate(userId);
    }

    @Override
    public void setServerEventListener(ServerEventListener serverEventListener) {
        this.serverEventListener = serverEventListener;
    }
}
